/*
 * File     : Padding.java
 * Created  : 1 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.type.structure;

import com.googlecode.dni.internal.DniInternal;

/**
 * <p>
 *  Represents the type of padding applied to structures.
 * </p>
 * <p>
 *  Padding of zero or more bytes is added before multi-byte types to align
 *  them in memory.  Different platforms have different alignment constraints
 *  &mdash;x86 and x86_64 are very liberal, but RISC architectures are
 *  traditionally limited to 32-bit- or 64-bit-aligned addresses.
 * </p>
 *
 * @author Matthew Wilson
 */
public enum Padding
{

    /** The default padding for the platform. */
    DEFAULT
    {
        /** {@inheritDoc} */
        @Override
        public int pad( final int offset, final int size )
        {
            return getDefault().pad( offset, size );
        }
    },

    /** No padding. */
    NONE
    {
        /** {@inheritDoc} */
        @Override
        public int pad( final int offset, final int size )
        {
            return offset;
        }
    },

    /**
     * All types that are two or more bytes in size will be at a 16-bit-aligned
     * offset.
     */
    FIXED_2( 1 ),

    /**
     * All types that are two or more bytes in size will be at a 32-bit-aligned
     * offset.
     */
    FIXED_4( 3 ),

    /**
     * All types that are two or more bytes in size will be at a 64-bit-aligned
     * offset.
     */
    FIXED_8( 7 ),

    /**
     * All types that are two or more bytes in size will be at a 128-bit-aligned
     * offset.
     */
    FIXED_16( 15 ),

    /**
     * All 16- and 32-bit types will be aligned to their intrinsic alignment,
     * and all larger types will be 32-bit aligned.
     */
    MINIMUM_4( false, 3 ),

    /**
     * All 16-, 32-, and 64-bit types will be aligned to their intrinsic
     * alignment, and all larger types will be 64-bit aligned.
     */
    MINIMUM_8( false, 7 ),

    /**
     * All 16-, 32-, 64-, and 128-bit types will be aligned to their intrinsic
     * alignment, and all larger types will be 128-bit aligned.
     */
    MINIMUM_16( false, 15 );

    private final boolean fixed;

    private final int alignMask;

    Padding()
    {
        this.fixed = false;
        this.alignMask = 0;
    }

    Padding( final int pad )
    {
        this.fixed = true;
        this.alignMask = pad;
    }

    Padding( final boolean fixed, final int alignMask )
    {
        this.fixed = fixed;
        this.alignMask = alignMask;
    }

    /**
     * <p>
     *  Determines the default padding for the platform.  This value is used
     *  where {@link #DEFAULT} is specified.
     * </p>
     *
     * @return the default padding; guaranteed not to be {@link #DEFAULT}
     */
    public static Padding getDefault()
    {
        return DniInternal.getDefaultPadding();
    }

    /**
     * Pads the given offset according to the padding rules for a unit of the
     * given size.
     *
     * @param offset
     *            the offset in bytes
     * @param size
     *            the size of a unit (must be a power of two)
     *
     * @return the adjusted offset; will be equal to or greater than the given
     *         offset
     */
    public int pad( final int offset, final int size )
    {
        if ( this.fixed )
        {
            // only types 16-bit or bigger will be padded
            if ( size == 1 )
            {
                return offset;
            }

            return ( offset + this.alignMask ) & ~this.alignMask;
        }

        int actualSize = 1 << ( 32 - Integer.numberOfLeadingZeros( size - 1 ) );

        // is it already padded?
        int padding = actualSize - ( offset & ( actualSize - 1 ) );
        if ( padding == actualSize )
        {
            return offset;
        }

        padding &= this.alignMask;

        return offset + padding;
    }

}
